/*
 * Copyright (C) 2010 Fred Barrie This program is free software: you can
 * redistribute it and/or modify it under the terms of the GNU General Public
 * License as published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version. This program is distributed
 * in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
 * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU General Public License for more details. You should have received
 * a copy of the GNU General Public License along with this program. If not, see
 * <http://www.gnu.org/licenses/>.
 */
package org.aergia.vinny.util;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.aergia.vinny.core.enums.DayOfWeek;
import org.aergia.vinny.core.enums.TimeFrame;

/**
 * @author fred
 */
public final class DateUtil {

	public static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd");
	public static final SimpleDateFormat DATE_TIME_FORMAT = new SimpleDateFormat("yyyy-MM-dd hh:mm a");
	public static final Calendar FAR_IN_FUTURE = new GregorianCalendar(9999, 11, 31, 23, 59);
	public static final Calendar FAR_IN_PAST = new GregorianCalendar(1, 1, 1, 0, 0);
	private static SimpleDateFormat HOUR_FORMAT = new SimpleDateFormat("hh:mm a");
	private static Pattern hourPattern = Pattern.compile("(\\d?\\d):(\\d\\d) ([A|P]M)");

	/**
	 * @param date
	 * @return
	 */
	public static String format(final Calendar date) {
		if (date == null) {
			return "";
		}
		return DateUtil.DATE_TIME_FORMAT.format(date.getTime());
	}

	public static String formatBackupDate() {
		final SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
		return format.format(DateUtil.getInstance().getTime());
	}

	public static String formatDuration(final long duration) {
		final long hours = duration / 60;
		final long minutes = (duration % 60);
		final StringBuilder sb = new StringBuilder();
		if (hours < 10) {
			sb.append("0");
		}
		sb.append(hours);
		sb.append(":");

		if (minutes < 10) {
			sb.append("0");
		}
		sb.append(minutes);
		return sb.toString();
	}

	public static String formatHour(final Calendar time) {
		return DateUtil.HOUR_FORMAT.format(time.getTime());
	}

	public static String formatHour(final long formatHour) {
		long hour = formatHour / 60;
		final long minute = formatHour % 60;
		String AM_PM = "AM";
		if (hour == 0) {
			hour = 12;
		} else if (hour >= 12) {
			AM_PM = "PM";
			if (hour > 12) {
				hour -= 12;
			}
		}
		return (hour < 10 ? "0" : "") + hour + ":" + (minute < 10 ? "0" : "") + minute + " " + AM_PM;
	}

	public static String formatSqlDate() {
		return DateUtil.formatSqlDate(DateUtil.getInstance());
	}

	public static String formatSqlDate(final Calendar date) {
		return DateUtil.DATE_FORMAT.format(date.getTime());
	}

	public static DayOfWeek getDayOfWeek(final Calendar date) {
		final int dayOfWeek = date.get(Calendar.DAY_OF_WEEK);
		return DayOfWeek.valueOf(dayOfWeek);
	}

	/**
	 * @param start
	 * @param end
	 * @return
	 */
	public static long getDifference(final Calendar start, final Calendar end) {
		final long startTime = start.getTimeInMillis() / (1000 * 60);
		final long endTime = end.getTimeInMillis() / (1000 * 60);
		return endTime - startTime;
	}

	public static Calendar getEndOfTimeFrame(final Calendar date, final TimeFrame timeFrame, final int firstMonthOfYear) {
		Calendar result = DateUtil.getStartOfTimeFrame(date, timeFrame, firstMonthOfYear);

		switch (timeFrame) {
			case SCAN:
				break;
			case MONTH:
				result.add(Calendar.MONTH, 1);
				result.add(Calendar.DAY_OF_YEAR, -1);
				break;
			case QUARTER:
				result.add(Calendar.MONTH, 3);
				result.add(Calendar.DAY_OF_YEAR, -1);
				break;
			case YEAR:
				result.add(Calendar.YEAR, 1);
				result.add(Calendar.DAY_OF_YEAR, -1);
				break;
			case PREVIOUS:
				result = DateUtil.FAR_IN_PAST;
				break;
		}

		if ((timeFrame != TimeFrame.SCAN) && (timeFrame != TimeFrame.PREVIOUS)) {
			result.set(Calendar.HOUR_OF_DAY, 23);
			result.set(Calendar.MINUTE, 59);
		}

		return result;
	}

	public static final Calendar getInstance() {
		return new GregorianCalendar();
	}

	/**
	 * @param scanTime
	 * @return
	 */
	public static Calendar getInstance(final Calendar scanTime) {
		final Calendar calendar = new GregorianCalendar();
		calendar.setTime(scanTime.getTime());
		return calendar;
	}

	/**
	 * @param offset
	 * @return a calendar adjusted by the offset
	 */
	public static final Calendar getInstance(final int offset) {
		final Calendar calendar = new GregorianCalendar();
		calendar.set(Calendar.MINUTE, offset);
		return calendar;
	}

	public static final Calendar getInstance(final String dateString) {

		Date date = new Date();
		try {
			date = DateUtil.DATE_TIME_FORMAT.parse(dateString);
		} catch (final Exception e) {
			// ignore;
		}
		final Calendar calendar = new GregorianCalendar();
		calendar.setTime(date);
		return calendar;
	}

	public static Calendar getMidnightYesterday() {
		final Calendar midnight = DateUtil.getInstance();
		midnight.set(Calendar.HOUR_OF_DAY, 0);
		midnight.set(Calendar.MINUTE, 0);
		midnight.set(Calendar.SECOND, 0);
		midnight.set(Calendar.MILLISECOND, 0);
		return midnight;
	}

	public static int getMinutesOfDay(final Calendar time) {
		final int hours = time.get(Calendar.HOUR_OF_DAY);
		final int minutes = time.get(Calendar.MINUTE);
		return (hours * 60) + minutes;
	}

	public static int getMonth(final String month) {
		if (month.equalsIgnoreCase("Jan")) {
			return 0;
		}
		if (month.equalsIgnoreCase("Feb")) {
			return 1;
		}
		if (month.equalsIgnoreCase("Mar")) {
			return 2;
		}
		if (month.equalsIgnoreCase("Apr")) {
			return 3;
		}
		if (month.equalsIgnoreCase("May")) {
			return 4;
		}
		if (month.equalsIgnoreCase("Jun")) {
			return 5;
		}
		if (month.equalsIgnoreCase("Jul")) {
			return 6;
		}
		if (month.equalsIgnoreCase("Aug")) {
			return 7;
		}
		if (month.equalsIgnoreCase("Sep")) {
			return 8;
		}
		if (month.equalsIgnoreCase("Oct")) {
			return 9;
		}
		if (month.equalsIgnoreCase("Nov")) {
			return 10;
		}
		if (month.equalsIgnoreCase("Dec")) {
			return 11;
		}
		return -1;
	}

	public static Date getSqlInstance(final String date) throws ParseException {
		return DateUtil.DATE_FORMAT.parse(date);
	}

	public static Calendar getStartOfTimeFrame(final Calendar date, final TimeFrame timeFrame, final int firstMonthOfYear) {
		Calendar result = DateUtil.getInstance(date);
		final int month = date.get(Calendar.MONTH);
		final int monthOffset = month - firstMonthOfYear;
		final int startMonth = firstMonthOfYear + ((monthOffset / 3) * 3) + ((monthOffset < 0) && (monthOffset > -3) ? -3 : 0);

		switch (timeFrame) {
			case SCAN:
				break;
			case MONTH:
				break;
			case QUARTER:
				result.set(Calendar.MONTH, startMonth);
				break;
			case YEAR:
				result.set(Calendar.MONTH, firstMonthOfYear);
				result.add(Calendar.YEAR, (monthOffset < 0 ? -1 : 0));
				break;
			case PREVIOUS:
				result = DateUtil.FAR_IN_PAST;
				break;
		}

		if ((timeFrame != TimeFrame.SCAN) && (timeFrame != TimeFrame.PREVIOUS)) {
			result.set(Calendar.DAY_OF_MONTH, 1);
			result.set(Calendar.HOUR_OF_DAY, 0);
			result.set(Calendar.MINUTE, 0);
		}

		return result;
	}

	/**
	 * @param time1
	 * @param time2
	 * @return true if time1 is before time2
	 */
	public static boolean isBefore(final Calendar time1, final Calendar time2) {
		return time1.before(time2);
	}

	/**
	 * @param scanTime
	 * @return true if the time is before yesterday
	 */
	public static boolean isBeforeMidnightYesterday(final Calendar time) {
		final Calendar yesterday = DateUtil.getMidnightYesterday();
		yesterday.set(Calendar.HOUR_OF_DAY, 0);
		yesterday.set(Calendar.MINUTE, 0);
		yesterday.set(Calendar.SECOND, 0);
		yesterday.set(Calendar.MILLISECOND, 0);
		return DateUtil.isBefore(time, yesterday);
	}

	public static boolean isBeforeYesterday(final Calendar time) {
		final Calendar yesterday = DateUtil.getInstance((int) -TimeUnit.DAYS.toMinutes(1));
		return DateUtil.isBefore(time, yesterday);
	}

	public static boolean isSameDay(final Calendar time1, final Calendar time2) {
		if (time1.get(Calendar.YEAR) != time2.get(Calendar.YEAR)) {
			return false;
		}
		if (time1.get(Calendar.DAY_OF_YEAR) != time2.get(Calendar.DAY_OF_YEAR)) {
			return false;
		}
		return true;
	}

	public static boolean isToday(final Calendar calendar) {
		final Calendar today = DateUtil.getInstance();
		if (today.get(Calendar.YEAR) != calendar.get(Calendar.YEAR)) {
			return false;
		}
		if (today.get(Calendar.DAY_OF_YEAR) != calendar.get(Calendar.DAY_OF_YEAR)) {
			return false;
		}
		return true;
	}

	/**
	 * Is the time2 within 24 hours of time1?
	 * 
	 * @param startTime
	 *            - initial time
	 * @param endTime
	 *            - time to check
	 * @return if time2 is within 24 hours of time1
	 */
	public static boolean isWithin24Hours(final Calendar startTime, final Calendar endTime) {
		return DateUtil.isWithinMinutes(startTime, endTime, TimeUnit.DAYS.toMinutes(1));

	}

	public static boolean isWithinMinutes(final Calendar startTime, final Calendar endTime, final long duration) {
		boolean result = false;
		final long startMillis = startTime.getTimeInMillis();
		final long endMillis = endTime.getTimeInMillis();

		final long delta = endMillis - startMillis;
		if ((delta > 0) && (delta < TimeUnit.MINUTES.toMillis(duration))) {
			result = true;
		}

		return result;
	}

	public static Calendar parseHour(final Calendar calendar, final String value) {
		final int minutes = DateUtil.parseHour(value);
		final Calendar result = DateUtil.getInstance(calendar);
		result.set(Calendar.HOUR_OF_DAY, minutes / 60);
		result.set(Calendar.MINUTE, minutes % 60);
		return result;
	}

	public static int parseHour(final String value) {
		final Matcher matcher = DateUtil.hourPattern.matcher(value);
		if (matcher.find()) {
			try {
				int hour = Integer.parseInt(matcher.group(1));
				final int minute = Integer.parseInt(matcher.group(2));
				if (hour == 12) {
					hour = 0;
				}
				if (matcher.group(3).startsWith("P")) {
					hour += 12;
				}
				return (hour * 60) + minute;
			} catch (final Exception e) {
				// ignore;
			}
		}
		return 0;
	}

	private DateUtil() {
		// empty
	}
}
